# Copyright 2021-2025 Avaiga Private Limited
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.

import os
import re
import typing as t
from io import StringIO

from taipy.gui.extension import ElementLibrary, PropertyType

I = "    "  # noqa: E741 - Indentation is 4 spaces


def generate_doc(library: ElementLibrary) -> str:  # noqa: C901
    """Generates the documentation for this Element Library."""
    stream = StringIO()

    def clean_doc_string(doc_string) -> t.Optional[str]:
        if not doc_string:
            return None
        # Remove trailing white spaces on each line
        lines = [s.rstrip() for s in doc_string.splitlines()]
        # 0 if first line is empty else 1
        first_line = 1 if lines and lines[0] else 0
        # Remove all first and last lines that are only blank
        lines = lines[
            next((i for i, x in enumerate(lines) if x.strip()), len(lines)) : len(lines)
            - next((i for i, x in enumerate(reversed(lines)) if x.strip()), len(lines))
        ]
        if lines and lines[0]:
            if len(lines) == 1:
                lines = [lines[0].strip()]
            else:
                # De-indent all lines, potentially avoiding the first one
                first_indent = next(i for i, line in enumerate(lines[first_line:]) if line and line[0].isspace())
                min_indent = min(
                    (len(line) - len(line.lstrip())) for line in lines[first_indent + first_line :] if line
                )
                lines = lines[:first_line] + [line[min_indent:] if line else "" for line in lines[first_line:]]
            return "\n".join(lines)
        else:
            return None  # pragma: no cover

    print("# ----------------------------------------------------------------------", file=stream)
    print("# Generated by taipy.gui.extension module", file=stream)
    print("# ----------------------------------------------------------------------", file=stream)
    print("import typing as t", file=stream)
    tgb_header = library.get_tgb_header()
    if tgb_header:
        print(tgb_header, file=stream)
    print("", file=stream)
    first_element = True
    for element_name, element in library.get_elements().items():
        if first_element:
            first_element = False
        else:
            print("\n", file=stream)
        property_names: list[str] = []
        parameters: list[str] = []
        property_doc = {}
        default_property_found = False
        for property_name, property in element.attributes.items():
            property_names.append(property_name)
            prop_def_value = property.default_value
            prop_type = property.type_hint
            if prop_type:
                prop_type = re.sub(r"\b(?<!t\.)\b(Optional|Union)\[", r"t.\1[", prop_type)
            else:
                # Could use 'match' with Python >= 3.10
                prop_type = "t.Union[str, t.Any]"
                if property.property_type in [PropertyType.boolean, PropertyType.dynamic_boolean]:
                    prop_type = "t.Union[bool, str]"
                elif property.property_type in [PropertyType.string, PropertyType.dynamic_string]:
                    prop_type = "str"
                    if prop_def_value:
                        prop_def_value = f'"{str(prop_def_value)}"'
                elif property.property_type in [PropertyType.dict, PropertyType.dynamic_dict]:
                    prop_type = "t.Union[dict, str]"
                    if prop_def_value:
                        prop_def_value = f'"{str(prop_def_value)}"'
                if prop_def_value is None:
                    prop_def_value = "None"
                prop_type = f"t.Optional[{prop_type}]"
            desc = f"{property_name}: {prop_type} = {prop_def_value}"
            if property_name == element.default_attribute:
                parameters.insert(0, desc)
                default_property_found = True
            else:
                parameters.append(desc)
            if doc_string := clean_doc_string(property.doc_string):
                property_doc[property_name] = doc_string
        if default_property_found and len(parameters) > 1:
            parameters.insert(1, "*")
        doc_string = clean_doc_string(element.doc_string)
        documentation = ""
        if doc_string:
            lines = doc_string.splitlines()
            documentation = f'{I}"""{lines.pop(0)}\n'
            while lines:
                line = lines.pop(0)
                documentation += f"{I}{line}\n" if line else "\n"
        else:
            documentation = f'{I}"""\n' if property_doc else ""
        if property_doc:
            documentation += f"\n{I}### Parameters:\n"
            for property_name in property_names:
                doc_string = property_doc.get(property_name)
                if doc_string:
                    lines = doc_string.splitlines()
                    documentation += f"{I}`{property_name}`: {lines.pop(0)}\n"
                    for line in lines:
                        documentation += f"{I * 2}{line}\n" if line else "\n"
                else:
                    documentation += f"{I}`{property_name}`: ...\n"
                documentation += "\n"
        if documentation:
            documentation += f'{I}"""\n'
        parameters_list = ",\n".join([f"{I}{p}" for p in parameters])
        print(f"def {element_name}(\n{parameters_list},\n) -> None:\n{documentation}{I}...", file=stream)

    return stream.getvalue()


def _generate_tgb(package_root_dir: str, output_path: str, error_fn: t.Callable[[str], None]) -> int:
    """Generates the Taipy GUI Page Builder API for a Taipy GUI extension package."""

    from importlib import import_module
    from inspect import getmembers, isclass

    # Remove potential directory separator at the end of the package root dir
    if package_root_dir[-1] == "/" or package_root_dir[-1] == "\\":  # pragma: no cover
        package_root_dir = package_root_dir[:-1]
    module = None
    try:
        module = import_module(package_root_dir)
    except Exception as e:  # pragma: no cover
        error_fn(f"Couldn't open module '{package_root_dir}' ({e})")
    library: t.Optional[ElementLibrary] = None
    for _, member in getmembers(module, lambda o: isclass(o) and issubclass(o, ElementLibrary)):
        if library:  # pragma: no cover
            error_fn("Extension contains more than one ElementLibrary")
        library = member()
    if library is None:  # pragma: no cover
        error_fn("Extension does not contain any ElementLibrary")
        return 1  # To avoid having to deal with this case in the following code

    if (pyi_path := output_path) is None:  # pragma: no cover
        pyi_path = os.path.join(package_root_dir, "__init__.pyi")
    pyi_file = None
    try:
        pyi_file = open(pyi_path, "w")
    except Exception as e:  # pragma: no cover
        error_fn(f"Couldn't open Python Interface Definition file '{pyi_path}' ({e})")

    print(f"Inspecting extension library '{library.get_name()}'")  # noqa: T201
    content = generate_doc(library)

    if pyi_file:
        print(content, file=pyi_file, end="")
        pyi_file.close()
    print(f"File '{pyi_path}' was updated.")  # noqa: T201
    return 0


# For the Pylance language server
__all__ = ["_generate_tgb"]
